D:\git\skunkworks\herald-for-cpp\herald-tests\analysisrunner-tests.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2021 Herald project contributors |
2 | | // SPDX-License-Identifier: Apache-2.0 |
3 | | // |
4 | | |
5 | | #include "catch.hpp" |
6 | | |
7 | | #include "herald/herald.h" |
8 | | |
9 | | #include <utility> |
10 | | #include <iostream> |
11 | | |
12 | | using namespace herald::analysis::sampling; |
13 | | using namespace herald::datatype; |
14 | | |
15 | | template <std::size_t Sz> |
16 | | struct DummyRSSISource { |
17 | | using value_type = Sample<RSSI>; // allows AnalysisRunner to introspect this class at compile time |
18 | | |
19 | | DummyRSSISource(const std::size_t srcDeviceKey, SampleList<Sample<RSSI>,Sz>&& data) |
20 | 4 | : key(srcDeviceKey), data(std::move(data)), lastAddedAt(0), lastRunAdded(0), hasRan(false) {}; |
21 | 4 | ~DummyRSSISource() = default; |
22 | | |
23 | | template <typename RunnerT> |
24 | 14 | void run(std::uint64_t timeTo, RunnerT& runner) { |
25 | 14 | // push through data at default rate |
26 | 14 | lastRunAdded = 0; |
27 | 121 | for (auto& v: data) { |
28 | 121 | // devList.push(v.taken,v.value); // copy data over (It's unusual taking a SampleList and sending to a SampleList) |
29 | 121 | auto sampleTime = v.taken.secondsSinceUnixEpoch(); |
30 | 121 | // Only push data that hasn't been pushed yet, otherwise we get an ever increasing sample list |
31 | 121 | if ((!hasRan || sampleTime > lastAddedAt100 ) && (sampleTime <= timeTo)62 ) { |
32 | 20 | lastRunAdded++; |
33 | 20 | runner.template newSample<RSSI>(key,v); |
34 | 20 | } |
35 | 121 | } |
36 | 14 | runner.run(Date(timeTo)); |
37 | 14 | lastAddedAt = timeTo; |
38 | 14 | hasRan = true; |
39 | 14 | } |
40 | | |
41 | 14 | std::uint64_t getLastRunAdded() { |
42 | 14 | return lastRunAdded; |
43 | 14 | } |
44 | | |
45 | | private: |
46 | | std::size_t key; |
47 | | SampleList<Sample<RSSI>,Sz> data; |
48 | | std::uint64_t lastAddedAt; |
49 | | std::uint64_t lastRunAdded; |
50 | | bool hasRan; |
51 | | }; |
52 | | |
53 | | struct DummyDistanceDelegate /* : herald::analysis::AnalysisDelegate */ { |
54 | | using value_type = Distance; |
55 | | |
56 | 13 | DummyDistanceDelegate() : lastSampledID(0), distances() {}; |
57 | | DummyDistanceDelegate(const DummyDistanceDelegate&) = delete; // copy ctor deleted |
58 | 6 | DummyDistanceDelegate(DummyDistanceDelegate&& other) noexcept : lastSampledID(other.lastSampledID), distances(std::move(other.distances)) {} // move ctor |
59 | 19 | ~DummyDistanceDelegate() {}; |
60 | | |
61 | 6 | DummyDistanceDelegate& operator=(DummyDistanceDelegate&& other) noexcept { |
62 | 6 | lastSampledID = other.lastSampledID; |
63 | 6 | std::swap(distances,other.distances); |
64 | 6 | return *this; |
65 | 6 | } |
66 | | |
67 | | // specific override of template |
68 | 8 | void newSample(SampledID sampled, Sample<Distance> sample) { |
69 | 8 | lastSampledID = sampled; |
70 | 8 | distances.push(sample); |
71 | 8 | } |
72 | | |
73 | 0 | void reset() { |
74 | 0 | distances.clear(); |
75 | 0 | lastSampledID = 0; |
76 | 0 | } |
77 | | |
78 | | // Test only methods |
79 | 6 | SampledID lastSampled() { |
80 | 6 | return lastSampledID; |
81 | 6 | } |
82 | | |
83 | 6 | const SampleList<Sample<Distance>,25>& samples() { |
84 | 6 | return distances; |
85 | 6 | } |
86 | | |
87 | | private: |
88 | | SampledID lastSampledID; |
89 | | SampleList<Sample<Distance>,25> distances; |
90 | | }; |
91 | | |
92 | | |
93 | 1 | TEST_CASE("variantset-basic", "[variantset][basic]") { |
94 | 1 | SECTION("variantset-basic") { |
95 | 1 | herald::analysis::VariantSet<int,double> vs; |
96 | 1 | REQUIRE(vs.size() == 2); |
97 | 1 | int intValue = vs.get<int>(); |
98 | 1 | REQUIRE(intValue == 0); |
99 | 1 | double dblValue = vs.get<double>(); |
100 | 1 | REQUIRE(dblValue == 0.0); |
101 | 1 | } |
102 | 1 | } |
103 | | |
104 | 1 | TEST_CASE("variantset-lists", "[variantset][lists]") { |
105 | 1 | SECTION("variantset-lists") { |
106 | 1 | herald::analysis::VariantSet<SampleList<Sample<int>,15>,SampleList<Sample<double>,15>> vs; |
107 | 1 | REQUIRE(vs.size() == 2); |
108 | 1 | SampleList<Sample<int>,15>& intValue = vs.get<SampleList<Sample<int>,15>>(); |
109 | 1 | REQUIRE(intValue.size() == 0); |
110 | 1 | SampleList<Sample<double>,15>& dblValue = vs.get<SampleList<Sample<double>,15>>(); |
111 | 1 | REQUIRE(dblValue.size() == 0); |
112 | 1 | |
113 | 1 | intValue.push(0,12); |
114 | 1 | dblValue.push(10,14.3); |
115 | 1 | dblValue.push(20,15.3); |
116 | 1 | |
117 | 1 | SampleList<Sample<int>,15>& intValue2 = vs.get<SampleList<Sample<int>,15>>(); |
118 | 1 | REQUIRE(intValue2.size() == 1); |
119 | 1 | SampleList<Sample<double>,15>& dblValue2 = vs.get<SampleList<Sample<double>,15>>(); |
120 | 1 | REQUIRE(dblValue2.size() == 2); |
121 | 1 | |
122 | 1 | } |
123 | 1 | } |
124 | | |
125 | 1 | TEST_CASE("variantset-listmanager", "[variantset][listmanager]") { |
126 | 1 | SECTION("variantset-listmanager") { |
127 | 1 | using herald::analysis::ListManager; |
128 | 1 | herald::analysis::VariantSet<ListManager<int,15>,ListManager<double,15>> vs; |
129 | 1 | REQUIRE(vs.size() == 2); |
130 | 1 | ListManager<int,15>& intValue = vs.template get<ListManager<int,15>>(); |
131 | 1 | REQUIRE(intValue.size() == 0); |
132 | 1 | ListManager<double,15>& dblValue = vs.template get<ListManager<double,15>>(); |
133 | 1 | REQUIRE(dblValue.size() == 0); |
134 | 1 | |
135 | 1 | SampleList<Sample<int>,15>& intList = intValue.list(1234); // list for entity being sampled with SampledId=1234 |
136 | 1 | REQUIRE(intList.size() == 0); |
137 | 1 | SampleList<Sample<double>,15>& dblList = dblValue.list(5678); // list for entity being sampled with SampledId=1234 |
138 | 1 | REQUIRE(dblList.size() == 0); |
139 | 1 | |
140 | 1 | intList.push(Sample(0,12)); |
141 | 1 | dblList.push(Sample(10,14.3)); |
142 | 1 | dblList.push(Sample(20,15.3)); |
143 | 1 | |
144 | 1 | ListManager<int,15>& intValue2 = vs.template get<ListManager<int,15>>(); |
145 | 1 | REQUIRE(intValue2.size() == 1); |
146 | 1 | ListManager<double,15>& dblValue2 = vs.template get<ListManager<double,15>>(); |
147 | 1 | REQUIRE(dblValue2.size() == 1); |
148 | 1 | |
149 | 1 | SampleList<Sample<int>,15>& intList2 = intValue2.list(1234); // list for entity being sampled with SampledId=1234 |
150 | 1 | REQUIRE(intList2.size() == 1); |
151 | 1 | SampleList<Sample<double>,15>& dblList2 = dblValue2.list(5678); // list for entity being sampled with SampledId=1234 |
152 | 1 | REQUIRE(dblList2.size() == 2); |
153 | 1 | |
154 | 1 | } |
155 | 1 | } |
156 | | |
157 | | /// Null test case with zero data, no failures, correct summary output |
158 | 1 | TEST_CASE("analysisrunner-nodata", "[analysisrunner][nodata]") { |
159 | 1 | SECTION("analysisrunner-nodata") { |
160 | 1 | SampleList<Sample<RSSI>,25> srcData; |
161 | 1 | DummyRSSISource src(1234,std::move(srcData)); |
162 | 1 | |
163 | 1 | herald::analysis::algorithms::distance::FowlerBasicAnalyser distanceAnalyser(30, -50, -24); |
164 | 1 | |
165 | 1 | DummyDistanceDelegate myDelegate; |
166 | 1 | herald::analysis::AnalysisDelegateManager adm(std::move(myDelegate)); // NOTE: myDelegate MOVED FROM and no longer accessible |
167 | 1 | herald::analysis::AnalysisProviderManager apm(std::move(distanceAnalyser)); // NOTE: distanceAnalyser MOVED FROM and no longer accessible |
168 | 1 | |
169 | 1 | herald::analysis::AnalysisRunner< |
170 | 1 | herald::analysis::AnalysisDelegateManager<DummyDistanceDelegate>, |
171 | 1 | herald::analysis::AnalysisProviderManager<herald::analysis::algorithms::distance::FowlerBasicAnalyser>, |
172 | 1 | RSSI,Distance |
173 | 1 | > runner(adm, apm); // just for Sample<RSSI> types, and their produced output (Sample<Distance>) |
174 | 1 | |
175 | 1 | src.run(140,runner); |
176 | 1 | REQUIRE(src.getLastRunAdded() == 0); // No data, no run |
177 | 1 | |
178 | 1 | auto& delegateRef = adm.get<DummyDistanceDelegate>(); |
179 | 1 | REQUIRE(delegateRef.lastSampled() == 0); // not ran, so 0 |
180 | 1 | |
181 | 1 | auto& samples = delegateRef.samples(); |
182 | 1 | REQUIRE(samples.size() == 0); // 0 as there is no source data |
183 | 1 | } |
184 | 1 | } |
185 | | |
186 | | /// Single data item use case with 1 data item, no failures, correct summary output |
187 | 1 | TEST_CASE("analysisrunner-singledataitem", "[analysisrunner][singledataitem]") { |
188 | 1 | SECTION("analysisrunner-singledataitem") { |
189 | 1 | SampleList<Sample<RSSI>,25> srcData; |
190 | 1 | srcData.push(50,-55); |
191 | 1 | DummyRSSISource src(1234,std::move(srcData)); |
192 | 1 | |
193 | 1 | herald::analysis::algorithms::distance::FowlerBasicAnalyser distanceAnalyser(30, -50, -24); |
194 | 1 | |
195 | 1 | DummyDistanceDelegate myDelegate; |
196 | 1 | herald::analysis::AnalysisDelegateManager adm(std::move(myDelegate)); // NOTE: myDelegate MOVED FROM and no longer accessible |
197 | 1 | herald::analysis::AnalysisProviderManager apm(std::move(distanceAnalyser)); // NOTE: distanceAnalyser MOVED FROM and no longer accessible |
198 | 1 | |
199 | 1 | herald::analysis::AnalysisRunner< |
200 | 1 | herald::analysis::AnalysisDelegateManager<DummyDistanceDelegate>, |
201 | 1 | herald::analysis::AnalysisProviderManager<herald::analysis::algorithms::distance::FowlerBasicAnalyser>, |
202 | 1 | RSSI,Distance |
203 | 1 | > runner(adm, apm); // just for Sample<RSSI> types, and their produced output (Sample<Distance>) |
204 | 1 | |
205 | 1 | src.run(140,runner); |
206 | 1 | REQUIRE(src.getLastRunAdded() == 1); // Single data item |
207 | 1 | |
208 | 1 | auto& delegateRef = adm.get<DummyDistanceDelegate>(); |
209 | 1 | REQUIRE(delegateRef.lastSampled() == 1234); // ran once, past 50, for SampleID=1234 |
210 | 1 | |
211 | 1 | auto& samples = delegateRef.samples(); |
212 | 1 | REQUIRE(samples.size() == 1); // 1 as single data item |
213 | 1 | } |
214 | 1 | } |
215 | | |
216 | | /// [Who] As a DCT app developer |
217 | | /// [What] I want to link my live application data to an analysis runner easily |
218 | | /// [Value] So I don't have to write plumbing code for Herald itself |
219 | | /// |
220 | | /// [Who] As a DCT app developer |
221 | | /// [What] I want to periodically run analysis aggregates automatically |
222 | | /// [Value] So I don't miss any information, and have accurate, regular, samples |
223 | 1 | TEST_CASE("analysisrunner-basic", "[analysisrunner][basic]") { |
224 | 1 | SECTION("analysisrunner-basic") { |
225 | 1 | SampleList<Sample<RSSI>,25> srcData; |
226 | 1 | srcData.push(10,-55); |
227 | 1 | srcData.push(20,-55); |
228 | 1 | srcData.push(30,-55); |
229 | 1 | srcData.push(40,-55); |
230 | 1 | srcData.push(50,-55); |
231 | 1 | srcData.push(60,-55); |
232 | 1 | srcData.push(70,-55); |
233 | 1 | srcData.push(80,-55); |
234 | 1 | srcData.push(90,-55); |
235 | 1 | srcData.push(100,-55); |
236 | 1 | DummyRSSISource src(1234,std::move(srcData)); |
237 | 1 | |
238 | 1 | herald::analysis::algorithms::distance::FowlerBasicAnalyser distanceAnalyser(30, -50, -24); |
239 | 1 | |
240 | 1 | DummyDistanceDelegate myDelegate; |
241 | 1 | herald::analysis::AnalysisDelegateManager adm(std::move(myDelegate)); // NOTE: myDelegate MOVED FROM and no longer accessible |
242 | 1 | herald::analysis::AnalysisProviderManager apm(std::move(distanceAnalyser)); // NOTE: distanceAnalyser MOVED FROM and no longer accessible |
243 | 1 | |
244 | 1 | herald::analysis::AnalysisRunner< |
245 | 1 | herald::analysis::AnalysisDelegateManager<DummyDistanceDelegate>, |
246 | 1 | herald::analysis::AnalysisProviderManager<herald::analysis::algorithms::distance::FowlerBasicAnalyser>, |
247 | 1 | RSSI,Distance |
248 | 1 | > runner(adm, apm); // just for Sample<RSSI> types, and their produced output (Sample<Distance>) |
249 | 1 | |
250 | 1 | // run at different times and ensure that it only actually runs three times (sample size == 3) |
251 | 1 | src.run(20,runner); |
252 | 1 | REQUIRE(src.getLastRunAdded() == 2); // 10, 20 - THIS IS ZERO BUT MUST BE 2!!! |
253 | 1 | src.run(40,runner); // Runs here, because we have data for 10,20,>>30<<,40 <- next run time based on this 'latest' data time |
254 | 1 | REQUIRE(src.getLastRunAdded() == 2); // 30, 40 |
255 | 1 | src.run(60,runner); |
256 | 1 | REQUIRE(src.getLastRunAdded() == 2); // 50, 60 |
257 | 1 | src.run(80,runner); // Runs here because we have extra data for 50,60,>>70<<,80 <- next run time based on this 'latest' data time |
258 | 1 | REQUIRE(src.getLastRunAdded() == 2); // 70, 80 |
259 | 1 | src.run(95,runner); |
260 | 1 | REQUIRE(src.getLastRunAdded() == 1); // 90 |
261 | 1 | |
262 | 1 | auto& delegateRef = adm.get<DummyDistanceDelegate>(); |
263 | 1 | REQUIRE(delegateRef.lastSampled() == 1234); |
264 | 1 | |
265 | 1 | auto& samples = delegateRef.samples(); |
266 | 1 | REQUIRE(samples.size() == 2); // didn't reach 4x30 seconds, so no tenth sample, and didn't run at 60 because previous run was at time 40 |
267 | 1 | REQUIRE(samples[0].taken.secondsSinceUnixEpoch() == 40); |
268 | 1 | REQUIRE(samples[0].value != 0.0); |
269 | 1 | REQUIRE(samples[1].taken.secondsSinceUnixEpoch() == 80); |
270 | 1 | REQUIRE(samples[1].value != 0.0); |
271 | 1 | |
272 | 1 | // Let's see the total memory in use... |
273 | 1 | std::cout << "AnalysisRunner::RAM = " << sizeof(runner) << std::endl; |
274 | 1 | } |
275 | 1 | } |
276 | | |
277 | | |
278 | | /// [Who] As a DCT app developer |
279 | | /// [What] I want to link my live application data to an analysis runner easily |
280 | | /// [Value] So I don't have to write plumbing code for Herald itself |
281 | | /// |
282 | | /// [Who] As a DCT app developer |
283 | | /// [What] I want to periodically run analysis aggregates automatically |
284 | | /// [Value] So I don't miss any information, and have accurate, regular, samples |
285 | 1 | TEST_CASE("analysisrunner-nonewdata", "[analysisrunner][nonewdata]") { |
286 | 1 | SECTION("analysisrunner-nonewdata") { |
287 | 1 | SampleList<Sample<RSSI>,25> srcData; |
288 | 1 | srcData.push(10,-55); |
289 | 1 | srcData.push(20,-55); |
290 | 1 | srcData.push(30,-55); |
291 | 1 | srcData.push(40,-55); |
292 | 1 | srcData.push(50,-55); |
293 | 1 | srcData.push(60,-55); |
294 | 1 | srcData.push(70,-55); |
295 | 1 | srcData.push(80,-55); |
296 | 1 | srcData.push(90,-55); |
297 | 1 | srcData.push(100,-55); |
298 | 1 | DummyRSSISource src(1234,std::move(srcData)); |
299 | 1 | |
300 | 1 | herald::analysis::algorithms::distance::FowlerBasicAnalyser distanceAnalyser(30, -50, -24); |
301 | 1 | |
302 | 1 | DummyDistanceDelegate myDelegate; |
303 | 1 | herald::analysis::AnalysisDelegateManager adm(std::move(myDelegate)); // NOTE: myDelegate MOVED FROM and no longer accessible |
304 | 1 | herald::analysis::AnalysisProviderManager apm(std::move(distanceAnalyser)); // NOTE: distanceAnalyser MOVED FROM and no longer accessible |
305 | 1 | |
306 | 1 | herald::analysis::AnalysisRunner< |
307 | 1 | herald::analysis::AnalysisDelegateManager<DummyDistanceDelegate>, |
308 | 1 | herald::analysis::AnalysisProviderManager<herald::analysis::algorithms::distance::FowlerBasicAnalyser>, |
309 | 1 | RSSI,Distance |
310 | 1 | > runner(adm, apm); // just for Sample<RSSI> types, and their produced output (Sample<Distance>) |
311 | 1 | |
312 | 1 | // run at different times and ensure that it only actually runs three times (sample size == 3) |
313 | 1 | src.run(20,runner); |
314 | 1 | REQUIRE(src.getLastRunAdded() == 2); // 10, 20 |
315 | 1 | src.run(40,runner); // Runs here, because we have data for 10,20,>>30<<,40 <- next run time based on this 'latest' data time |
316 | 1 | REQUIRE(src.getLastRunAdded() == 2); // 30, 40 |
317 | 1 | src.run(60,runner); |
318 | 1 | REQUIRE(src.getLastRunAdded() == 2); // 50, 60 |
319 | 1 | src.run(80,runner); // Runs here because we have extra data for 50,60,>>70<<,80 <- next run time based on this 'latest' data time |
320 | 1 | REQUIRE(src.getLastRunAdded() == 2); // 70, 80 |
321 | 1 | src.run(95,runner); |
322 | 1 | REQUIRE(src.getLastRunAdded() == 1); // 90 |
323 | 1 | |
324 | 1 | // Now ensure that running runner past end of data does not cause result to change |
325 | 1 | src.run(115,runner); // Run here because we have past 80 + 30 |
326 | 1 | REQUIRE(src.getLastRunAdded() == 1); // 100 |
327 | 1 | src.run(150,runner); // Should not run here as there's no new data (even though we're at > 115 + 30) |
328 | 1 | REQUIRE(src.getLastRunAdded() == 0); // No new data |
329 | 1 | |
330 | 1 | auto& delegateRef = adm.get<DummyDistanceDelegate>(); |
331 | 1 | REQUIRE(delegateRef.lastSampled() == 1234); |
332 | 1 | |
333 | 1 | auto& samples = delegateRef.samples(); |
334 | 1 | REQUIRE(samples.size() == 3); // 150 should not result in a run after 145 (min time delay) |
335 | 1 | REQUIRE(samples[0].taken.secondsSinceUnixEpoch() == 40); |
336 | 1 | REQUIRE(samples[0].value != 0.0); |
337 | 1 | REQUIRE(samples[1].taken.secondsSinceUnixEpoch() == 80); |
338 | 1 | REQUIRE(samples[1].value != 0.0); |
339 | 1 | REQUIRE(samples[2].taken.secondsSinceUnixEpoch() == 100); // Last data was at 100, not 115, so it takes that time |
340 | 1 | REQUIRE(samples[2].value != 0.0); |
341 | 1 | // REQUIRE(samples[3].taken.secondsSinceUnixEpoch() == 150); |
342 | 1 | // REQUIRE(samples[3].value != 0.0); |
343 | 1 | } |
344 | 1 | } |
345 | | |
346 | | |